package main

import (
	"fmt"
	"io"
	"strings"
)

// 独立的自定义类型: 这个类型的所有方法都是自己显示实现的。
// 继承->组合

// 类型嵌入: 就是在一个类型中嵌入其他类型
// Go 语言支持两种类型嵌入: 分别是 接口类型的类型嵌入 和 结构体类型的类型嵌入

/**
 * 接口类型的类型嵌入
 * 接口类型嵌入的语义就是新接口类型(如接口类型I)将嵌入的接口类型(如接口类型E)的方法集合, 并入到自己的方法集合中。
 *
 * 在Go1.14之前是有约束的: 如果心接口类型嵌入了多个接口类型, 这些嵌入的接口类型的方法不能有交集, 同时嵌入的接口类型的方法集合中的
 * 方法名字也不能与新接口中的其他方法同名。
 **/
type E interface {
	M1()
	M2()
}

type I interface {
	M1()
	M2()
	M3()
}

// 接口类型I方法集合中的M1和M2, 与接口类型E的方法集合中的方法完全相同, 这种情况下, 就可以使用接口类型E替代上面接口类型I定义中M1和M2
type I0 interface {
	E
	M3()
}

/**
 * 结构体类型的类型嵌入
 * 这种以某个类型名、类型的指针类型名或接口类型名, 直接作为结构体字段的方式就叫做结构体的类型嵌入, 这些字段也被叫做嵌入字段(Embedded Field)
 * 结构体类型的方法集合, 包含嵌入的接口类型的方法集合
 *
 * 基于已存在的接口类型新defined的类型包含原接口类型的方法
 * 基于已存在的非接口类型新defined的类型不包含原接口类型的方法
 * 无论原类型是接口类型还是非接口类型, 类型别名都与原类型拥有完全相同的方法集合
 **/

type T1 int
type t2 struct {
	n int
	m int
}

type I1 interface {
	M1()
}

/**
 * 这里 T1, t2, I1 既代表字段的名字, 也代表字段的类型
 **/
type S1 struct {
	T1  // 标识符T1表示字段名为T1, 它的类型为自定义类型T1
	*t2 // 标识符t2表示字段名为t2, 它的类型为自定义结构体t2的指针类型
	I1  // 标识符I1表示字段名为I1, 它的类型为接口类型I
	a   int
	b   string
}

type MyInt int

func (n *MyInt) Add(m int) {
	*n = *n + MyInt(m)
}

type t struct {
	a int
	b int
}

// 嵌入字段的可见性与嵌入字段的类型的可见性是一致的。(如果嵌入类型的名字是首字母大写的, 那么也就说明这个嵌入字段是可导出的。)
type S struct {
	*MyInt
	t
	io.Reader // Go语言规定如果结构体使用从其他包导入的类型作为嵌入字段, 那么这个嵌入字段的字段名就是Reader, 代表的类型为io.Reader
	s         string
	n         int
}

// 嵌入字段类型的底层类型不能为指针类型. 而且, 嵌入字段的名字在结构体定义也必须是唯一的, 这也意味着如果两个类型的名字相同, 它们无法同时作为嵌入字段放到同一个结构体中.

func main() {
	m := MyInt(17)
	r := strings.NewReader("hello, go")
	s := S{
		MyInt: &m,
		t: t{
			a: 1,
			b: 2,
		},
		Reader: r,
		s:      "demo",
	}

	var sl = make([]byte, len("hello, go"))
	// s.Reader.Read(sl)
	// <=>
	s.Read(sl)

	fmt.Println(string(sl)) // hello, go

	// s.MyInt.Add(5)
	// <=>
	s.Add(5)
	fmt.Println(*(s.MyInt)) // 22
}
